{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "eec680f5",
   "metadata": {},
   "source": [
    "# 중간 단계의 상태(State) 수동 업데이트\n",
    "\n",
    "LangGraph는 **중간 단계의 상태를 수동으로 업데이트** 할 수 있는 방안을 제공하고 있습니다.\n",
    "\n",
    "상태를 업데이트하면 **에이전트의 행동을 수정하여 경로를 제어** 할 수 있으며, 심지어 과거를 수정할 수도 있습니다. \n",
    "\n",
    "이 기능은 **에이전트의 실수를 수정** 하거나, **대체 경로를 탐색** 하거나, 특정 목표에 따라 **에이전트의 동작을 변경** 할 때 특히 유용합니다.\n",
    "\n",
    "- 참고: 본 튜토리얼에서 사용하는 에이전트는 이전 튜토리얼과 동일한 그래프를 정의합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "de9d9d8d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# API 키를 환경변수로 관리하기 위한 설정 파일\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "# API 키 정보 로드\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "6b5c6228",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "LangSmith 추적을 시작합니다.\n",
      "[프로젝트명]\n",
      "CH17-LangGraph-Modules\n"
     ]
    }
   ],
   "source": [
    "# LangSmith 추적을 설정합니다. https://smith.langchain.com\n",
    "# !pip install -qU langchain-teddynote\n",
    "from langchain_teddynote import logging\n",
    "\n",
    "# 프로젝트 이름을 입력합니다.\n",
    "logging.langsmith(\"CH17-LangGraph-Modules\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "e5d857b9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from typing import Annotated\n",
    "from typing_extensions import TypedDict\n",
    "\n",
    "from langchain_teddynote.tools.tavily import TavilySearch\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "from langgraph.graph.message import add_messages\n",
    "from langgraph.prebuilt import ToolNode, tools_condition\n",
    "from langchain_teddynote.graphs import visualize_graph\n",
    "\n",
    "\n",
    "########## 1. 상태 정의 ##########\n",
    "# 상태 정의\n",
    "class State(TypedDict):\n",
    "    # 메시지 목록 주석 추가\n",
    "    messages: Annotated[list, add_messages]\n",
    "\n",
    "\n",
    "########## 2. 도구 정의 및 바인딩 ##########\n",
    "# 도구 초기화\n",
    "tool = TavilySearch(max_results=3)\n",
    "\n",
    "# 도구 목록 정의\n",
    "tools = [tool]\n",
    "\n",
    "# LLM 초기화\n",
    "llm = ChatOpenAI(model=\"gpt-4o-mini\")\n",
    "\n",
    "# 도구와 LLM 결합\n",
    "llm_with_tools = llm.bind_tools(tools)\n",
    "\n",
    "\n",
    "########## 3. 노드 추가 ##########\n",
    "# 챗봇 함수 정의\n",
    "def chatbot(state: State):\n",
    "    # 메시지 호출 및 반환\n",
    "    return {\"messages\": [llm_with_tools.invoke(state[\"messages\"])]}\n",
    "\n",
    "\n",
    "# 상태 그래프 생성\n",
    "graph_builder = StateGraph(State)\n",
    "\n",
    "# 챗봇 노드 추가\n",
    "graph_builder.add_node(\"chatbot\", chatbot)\n",
    "\n",
    "\n",
    "# 도구 노드 생성 및 추가\n",
    "tool_node = ToolNode(tools=tools)\n",
    "\n",
    "# 도구 노드 추가\n",
    "graph_builder.add_node(\"tools\", tool_node)\n",
    "\n",
    "# 조건부 엣지\n",
    "graph_builder.add_conditional_edges(\n",
    "    \"chatbot\",\n",
    "    tools_condition,\n",
    ")\n",
    "\n",
    "########## 4. 엣지 추가 ##########\n",
    "\n",
    "# tools > chatbot\n",
    "graph_builder.add_edge(\"tools\", \"chatbot\")\n",
    "\n",
    "# START > chatbot\n",
    "graph_builder.add_edge(START, \"chatbot\")\n",
    "\n",
    "# chatbot > END\n",
    "graph_builder.add_edge(\"chatbot\", END)\n",
    "\n",
    "########## 5. 그래프 컴파일 ##########\n",
    "# 메모리 저장소 초기화\n",
    "memory = MemorySaver()\n",
    "\n",
    "# 그래프 빌더 컴파일\n",
    "graph = graph_builder.compile(checkpointer=memory)\n",
    "\n",
    "########## 6. 그래프 시각화 ##########\n",
    "# 그래프 시각화\n",
    "visualize_graph(graph)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "d0b61953",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.runnables import RunnableConfig\n",
    "\n",
    "# 질문\n",
    "question = \"LangGraph 가 무엇인지 조사하여 알려주세요!\"\n",
    "\n",
    "# 초기 입력 상태를 정의\n",
    "input = State(messages=[(\"user\", question)])\n",
    "\n",
    "# config 설정\n",
    "config = RunnableConfig(\n",
    "    configurable={\"thread_id\": \"1\"},  # 스레드 ID 설정\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ab2ab8a",
   "metadata": {},
   "source": [
    "우선 채널목록을 출력하여 **interrupt_before** 와 **interrupt_after** 를 적용할 수 있는 목록을 출력합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "005ec550",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['messages',\n",
       " '__start__',\n",
       " 'chatbot',\n",
       " 'tools',\n",
       " 'start:chatbot',\n",
       " 'branch:chatbot:tools_condition:chatbot',\n",
       " 'branch:chatbot:tools_condition:tools']"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 그래프 채널 목록 출력\n",
    "list(graph.channels)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "cf1b5314",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "LangGraph 가 무엇인지 조사하여 알려주세요!\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  tavily_web_search (call_uAZwKKbpIcMsKKOHI6aFIMty)\n",
      " Call ID: call_uAZwKKbpIcMsKKOHI6aFIMty\n",
      "  Args:\n",
      "    query: LangGraph\n"
     ]
    }
   ],
   "source": [
    "# 그래프 스트림 호출\n",
    "events = graph.stream(\n",
    "    input=input, config=config, interrupt_before=[\"tools\"], stream_mode=\"values\"\n",
    ")\n",
    "\n",
    "# 이벤트 반복 처리\n",
    "for event in events:\n",
    "    # 메시지가 이벤트에 포함된 경우\n",
    "    if \"messages\" in event:\n",
    "        # 마지막 메시지의 예쁜 출력\n",
    "        event[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ee8250e",
   "metadata": {},
   "source": [
    "현재 단계는 `ToolNode` 에 의해 중단되었습니다.\n",
    "\n",
    "가장 최근 메시지를 확인하면 `ToolNode` 가 검색을 수행하기 전 `query` 를 포함하고 있음을 알 수 있습니다.\n",
    "\n",
    "여기서는 `query` 가 단순하게 `LangGraph` 라는 단어만을 포함하고 있습니다. (기존의 질문은 `\"LangGraph 가 무엇인지 조사하여 알려주세요!\"` 였습니다.)\n",
    "\n",
    "당연하게도, 웹 검색 결과가 우리가 원하는 결과와 다를 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "731587a0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  tavily_web_search (call_uAZwKKbpIcMsKKOHI6aFIMty)\n",
      " Call ID: call_uAZwKKbpIcMsKKOHI6aFIMty\n",
      "  Args:\n",
      "    query: LangGraph\n"
     ]
    }
   ],
   "source": [
    "# 그래프 상태 스냅샷 생성\n",
    "snapshot = graph.get_state(config)\n",
    "\n",
    "# 가장 최근 메시지 추출\n",
    "last_message = snapshot.values[\"messages\"][-1]\n",
    "\n",
    "# 메시지 출력\n",
    "last_message.pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3dad6839",
   "metadata": {},
   "source": [
    "## 사람의 개입 (Human-in-the-loop)\n",
    "\n",
    "- `TavilySearch` 도구에서 **검색 결과** 를 수정\n",
    "\n",
    "우리는 종종 `ToolMessage` 의 결과가 마음에 들지 않는 경우가 있습니다.\n",
    "\n",
    "특히, 웹 검색하여 얻은 답변은 얼마든지 잘못된 정보가 포함될 수 있고, 이는 곧 챗봇의 답변에도 영향을 미칠 수 있습니다.\n",
    "\n",
    "만약, 사람이 중간에 개입하여 웹 검색 도구인 `Tavily Tool` 의 검색 결과인 `ToolMessage` 를 수정하여 LLM 에게 전달하고 싶다면 어떻게 해야 할까요?"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e301863",
   "metadata": {},
   "source": [
    "![](./image/langgraph-01.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f3a0d4cc",
   "metadata": {},
   "source": [
    "아래는 원래의 웹 검색 결과와는 조금 다른 수정한 가상의 웹 검색 결과를 만들어 보았습니다"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "0d8efa7d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[수정된 웹 검색 결과] \n",
      "LangGraph는 상태 기반의 다중 액터 애플리케이션을 LLM을 활용해 구축할 수 있도록 지원합니다.\n",
      "LangGraph는 사이클 흐름, 제어 가능성, 지속성, 클라우드 배포 기능을 제공하는 오픈 소스 라이브러리입니다.\n",
      "\n",
      "자세한 튜토리얼은 [LangGraph 튜토리얼](https://langchain-ai.github.io/langgraph/tutorials/) 과\n",
      "테디노트의 [랭체인 한국어 튜토리얼](https://wikidocs.net/233785) 을 참고하세요.\n"
     ]
    }
   ],
   "source": [
    "modified_search_result = \"\"\"[수정된 웹 검색 결과] \n",
    "LangGraph는 상태 기반의 다중 액터 애플리케이션을 LLM을 활용해 구축할 수 있도록 지원합니다.\n",
    "LangGraph는 사이클 흐름, 제어 가능성, 지속성, 클라우드 배포 기능을 제공하는 오픈 소스 라이브러리입니다.\n",
    "\n",
    "자세한 튜토리얼은 [LangGraph 튜토리얼](https://langchain-ai.github.io/langgraph/tutorials/) 과\n",
    "테디노트의 [랭체인 한국어 튜토리얼](https://wikidocs.net/233785) 을 참고하세요.\"\"\"\n",
    "\n",
    "print(modified_search_result)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a18cff2a",
   "metadata": {},
   "source": [
    "다음으로는 수정한 검색 결과를 `ToolMessage` 에 주입합니다.\n",
    "\n",
    "**중요**\n",
    "\n",
    "- 여기서 메시지를 수정하려면 수정하고자 하는 Message 와 일치하는 `tool_call_id` 를 지정해야 합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "52368d6e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "call_uAZwKKbpIcMsKKOHI6aFIMty\n"
     ]
    }
   ],
   "source": [
    "# 수정하고자 하는 `ToolMessage` 의 `tool_call_id` 추출\n",
    "tool_call_id = last_message.tool_calls[0][\"id\"]\n",
    "print(tool_call_id)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "1acabaac",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "\n",
      "[수정된 웹 검색 결과] \n",
      "LangGraph는 상태 기반의 다중 액터 애플리케이션을 LLM을 활용해 구축할 수 있도록 지원합니다.\n",
      "LangGraph는 사이클 흐름, 제어 가능성, 지속성, 클라우드 배포 기능을 제공하는 오픈 소스 라이브러리입니다.\n",
      "\n",
      "자세한 튜토리얼은 [LangGraph 튜토리얼](https://langchain-ai.github.io/langgraph/tutorials/) 과\n",
      "테디노트의 [랭체인 한국어 튜토리얼](https://wikidocs.net/233785) 을 참고하세요.\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.messages import AIMessage, ToolMessage\n",
    "\n",
    "new_messages = [\n",
    "    # LLM API의 도구 호출과 일치하는 ToolMessage 필요\n",
    "    ToolMessage(\n",
    "        content=modified_search_result,\n",
    "        tool_call_id=tool_call_id,\n",
    "    ),\n",
    "    # LLM의 응답에 직접적으로 내용 추가\n",
    "    # AIMessage(content=modified_search_result),\n",
    "]\n",
    "\n",
    "new_messages[-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b970602d",
   "metadata": {},
   "source": [
    "### StateGraph의 `update_state` 메서드\n",
    "\n",
    "`update_state` 메서드는 주어진 값으로 그래프의 상태를 업데이트합니다. 이 메서드는 마치 `as_node`에서 값이 온 것처럼 동작합니다.\n",
    "\n",
    "**매개변수**\n",
    "\n",
    "- `config` (RunnableConfig): 실행 구성\n",
    "- `values` (Optional[Union[dict[str, Any], Any]]): 업데이트할 값들\n",
    "- `as_node` (Optional[str]): 값의 출처로 간주할 노드 이름. 기본값은 None\n",
    "\n",
    "**반환값**\n",
    "\n",
    "- RunnableConfig\n",
    "\n",
    "**주요 기능**\n",
    "\n",
    "1. 체크포인터를 통해 이전 상태를 로드하고 새로운 상태를 저장합니다.\n",
    "2. 서브그래프에 대한 상태 업데이트를 처리합니다.\n",
    "3. `as_node`가 지정되지 않은 경우, 마지막으로 상태를 업데이트한 노드를 찾습니다.\n",
    "4. 지정된 노드의 writer들을 실행하여 상태를 업데이트합니다.\n",
    "5. 업데이트된 상태를 체크포인트에 저장합니다.\n",
    "\n",
    "**주요 로직**\n",
    "\n",
    "1. 체크포인터를 확인하고, 없으면 ValueError를 발생시킵니다.\n",
    "2. 서브그래프에 대한 업데이트인 경우, 해당 서브그래프의 `update_state` 메서드를 호출합니다.\n",
    "3. 이전 체크포인트를 로드하고, 필요한 경우 `as_node`를 결정합니다.\n",
    "4. 지정된 노드의 writer들을 사용하여 상태를 업데이트합니다.\n",
    "5. 업데이트된 상태를 새로운 체크포인트로 저장합니다.\n",
    "\n",
    "**참고**\n",
    "\n",
    "- 이 메서드는 그래프의 상태를 수동으로 업데이트할 때 사용됩니다.\n",
    "- 체크포인터를 사용하여 상태의 버전 관리와 지속성을 보장합니다.\n",
    "- `as_node`를 지정하지 않으면 자동으로 결정되지만, 모호한 경우 오류가 발생할 수 있습니다.\n",
    "- 상태 업데이트 중 SharedValues에 쓰기 작업은 허용되지 않습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "15691b8c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(최근 1개의 메시지 출력)\n",
      "\n",
      "content='[수정된 웹 검색 결과] \\nLangGraph는 상태 기반의 다중 액터 애플리케이션을 LLM을 활용해 구축할 수 있도록 지원합니다.\\nLangGraph는 사이클 흐름, 제어 가능성, 지속성, 클라우드 배포 기능을 제공하는 오픈 소스 라이브러리입니다.\\n\\n자세한 튜토리얼은 [LangGraph 튜토리얼](https://langchain-ai.github.io/langgraph/tutorials/) 과\\n테디노트의 [랭체인 한국어 튜토리얼](https://wikidocs.net/233785) 을 참고하세요.' id='7eb0cf12-f253-44a8-b0f4-c2cbb718c172' tool_call_id='call_uAZwKKbpIcMsKKOHI6aFIMty'\n"
     ]
    }
   ],
   "source": [
    "graph.update_state(\n",
    "    # 업데이트할 상태 지정\n",
    "    config,\n",
    "    # 제공할 업데이트된 값. `State`의 메시지는 \"추가 전용\"으로 기존 상태에 추가됨\n",
    "    {\"messages\": new_messages},\n",
    "    as_node=\"tools\",\n",
    ")\n",
    "\n",
    "print(\"(최근 1개의 메시지 출력)\\n\")\n",
    "print(graph.get_state(config).values[\"messages\"][-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d0b868cb",
   "metadata": {},
   "source": [
    "이제 그래프가 완성되었습니다. \n",
    "\n",
    "최종 응답 메시지를 제공했기 때문입니다! \n",
    "\n",
    "상태 업데이트는 그래프 단계를 시뮬레이션하므로, 해당하는 `traces`도 생성합니다.\n",
    "\n",
    "`messages`를 사전 정의된 `add_messages` 함수로 `Annotated` 처리했습니다. (이는 그래프에 기존 목록을 직접 덮어쓰지 않고 항상 값을 추가합니다.)\n",
    "\n",
    "동일한 논리가 여기에도 적용되어, `update_state`에 전달된 메시지가 동일한 방식으로 메시지가 추가하게 됩니다.\n",
    "\n",
    "`update_state` 함수는 마치 그래프의 노드 중 하나인 것처럼 작동합니다! 기본적으로 업데이트 작업은 마지막으로 실행된 노드를 사용하지만, 아래에서 수동으로 지정할 수 있습니다. 업데이트를 추가하고 그래프에 \"chatbot\"에서 온 것처럼 처리하도록 지시해 봅시다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "3a4c1fbb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('chatbot',)"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "snapshot = graph.get_state(config)\n",
    "snapshot.next"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "36223336",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "\n",
      "[수정된 웹 검색 결과] \n",
      "LangGraph는 상태 기반의 다중 액터 애플리케이션을 LLM을 활용해 구축할 수 있도록 지원합니다.\n",
      "LangGraph는 사이클 흐름, 제어 가능성, 지속성, 클라우드 배포 기능을 제공하는 오픈 소스 라이브러리입니다.\n",
      "\n",
      "자세한 튜토리얼은 [LangGraph 튜토리얼](https://langchain-ai.github.io/langgraph/tutorials/) 과\n",
      "테디노트의 [랭체인 한국어 튜토리얼](https://wikidocs.net/233785) 을 참고하세요.\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "LangGraph는 상태 기반의 다중 액터 애플리케이션을 대형 언어 모델(LLM)을 활용하여 구축할 수 있도록 지원하는 오픈 소스 라이브러리입니다. 이 라이브러리는 사이클 흐름, 제어 가능성, 지속성 및 클라우드 배포 기능을 제공하여 개발자들이 복잡한 애플리케이션을 보다 쉽게 개발할 수 있게 돕습니다.\n",
      "\n",
      "자세한 튜토리얼은 [LangGraph 튜토리얼](https://langchain-ai.github.io/langgraph/tutorials/)과 테디노트의 [랭체인 한국어 튜토리얼](https://wikidocs.net/233785)에서 확인할 수 있습니다.\n"
     ]
    }
   ],
   "source": [
    "# `None`는 현재 상태에 아무것도 추가하지 않음\n",
    "events = graph.stream(None, config, stream_mode=\"values\")\n",
    "\n",
    "# 이벤트 반복 처리\n",
    "for event in events:\n",
    "    # 메시지가 이벤트에 포함된 경우\n",
    "    if \"messages\" in event:\n",
    "        # 마지막 메시지의 예쁜 출력\n",
    "        event[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad62e348",
   "metadata": {},
   "source": [
    "아래는 최종 답변의 상태도 수정하고자 할 경우 사용할 수 있는 코드입니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0889cbf7",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 이 함수가 작동할 노드 지정. 이 노드가 방금 실행된 것처럼 자동으로 처리 계속\n",
    "# graph.update_state(\n",
    "#     config,\n",
    "#     {\n",
    "#         \"messages\": [\n",
    "#             AIMessage(content=\"마지막으로 최종 메시지를 추가하여 마무리 합니다.\")\n",
    "#         ]\n",
    "#     },\n",
    "#     as_node=\"chatbot\",\n",
    "# )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "697183db",
   "metadata": {},
   "source": [
    "그럼, 그래프를 시각화하고 전체 출력을 확인해 보겠습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "458ada94",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/jpeg": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from langchain_teddynote.graphs import visualize_graph\n",
    "\n",
    "visualize_graph(graph)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a63f4cf",
   "metadata": {},
   "source": [
    "현재 상태를 이전과 같이 점검하여 체크포인트가 수동 업데이트를 반영하는지 확인합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "b2d31c27",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "LangGraph 가 무엇인지 조사하여 알려주세요!\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  tavily_web_search (call_uAZwKKbpIcMsKKOHI6aFIMty)\n",
      " Call ID: call_uAZwKKbpIcMsKKOHI6aFIMty\n",
      "  Args:\n",
      "    query: LangGraph\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "\n",
      "[수정된 웹 검색 결과] \n",
      "LangGraph는 상태 기반의 다중 액터 애플리케이션을 LLM을 활용해 구축할 수 있도록 지원합니다.\n",
      "LangGraph는 사이클 흐름, 제어 가능성, 지속성, 클라우드 배포 기능을 제공하는 오픈 소스 라이브러리입니다.\n",
      "\n",
      "자세한 튜토리얼은 [LangGraph 튜토리얼](https://langchain-ai.github.io/langgraph/tutorials/) 과\n",
      "테디노트의 [랭체인 한국어 튜토리얼](https://wikidocs.net/233785) 을 참고하세요.\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "LangGraph는 상태 기반의 다중 액터 애플리케이션을 대형 언어 모델(LLM)을 활용하여 구축할 수 있도록 지원하는 오픈 소스 라이브러리입니다. 이 라이브러리는 사이클 흐름, 제어 가능성, 지속성 및 클라우드 배포 기능을 제공하여 개발자들이 복잡한 애플리케이션을 보다 쉽게 개발할 수 있게 돕습니다.\n",
      "\n",
      "자세한 튜토리얼은 [LangGraph 튜토리얼](https://langchain-ai.github.io/langgraph/tutorials/)과 테디노트의 [랭체인 한국어 튜토리얼](https://wikidocs.net/233785)에서 확인할 수 있습니다.\n"
     ]
    }
   ],
   "source": [
    "# 그래프 상태 스냅샷 생성\n",
    "snapshot = graph.get_state(config)\n",
    "\n",
    "# 최근 세 개의 메시지 출력\n",
    "for message in snapshot.values[\"messages\"]:\n",
    "    message.pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "457828d2",
   "metadata": {},
   "source": [
    "진행할 다음 노드가 있는지 확인합니다. `()` 가 비어 있는 것으로 확인할 수 있습니다. 즉, 모든 과정이 정상적으로 진행되었음을 알 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "c6ea87c0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "()\n"
     ]
    }
   ],
   "source": [
    "# 다음 상태 출력\n",
    "print(snapshot.next)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "98fcbffe",
   "metadata": {},
   "source": [
    "## Interrupt 후 메시지 상태 업데이트 - 이어서 진행\n",
    "\n",
    "- `TavilySearch` 도구에서 **검색 쿼리** 를 수정\n",
    "\n",
    "이번에는 다음 노드로 진행하기 전 interrupt 를 발생시켜 중단하고, 상태(State) 를 갱신한 뒤 이어서 진행하는 방법을 살펴보겠습니다.\n",
    "\n",
    "먼저, 새로운 `thread_id` 를 생성합니다.\n",
    "\n",
    "여기서는 랜덤한 해시값을 생성하는 `generate_random_hash` 함수를 사용합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "5c7fe37d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "thread_id: 6c95af\n",
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "LangGraph 에 대해서 배워보고 싶습니다. 유용한 자료를 추천해 주세요!\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  tavily_web_search (call_xJVDxiPGNJcHmErUMrCEMxdF)\n",
      " Call ID: call_xJVDxiPGNJcHmErUMrCEMxdF\n",
      "  Args:\n",
      "    query: LangGraph 소개 및 자료\n"
     ]
    }
   ],
   "source": [
    "from langchain_teddynote.graphs import generate_random_hash\n",
    "\n",
    "thread_id = generate_random_hash()\n",
    "print(f\"thread_id: {thread_id}\")\n",
    "\n",
    "question = \"LangGraph 에 대해서 배워보고 싶습니다. 유용한 자료를 추천해 주세요!\"\n",
    "\n",
    "# 초기 입력 상태를 정의\n",
    "input = State(messages=[(\"user\", question)])\n",
    "\n",
    "# 새로운 config 생성\n",
    "config = {\"configurable\": {\"thread_id\": thread_id}}\n",
    "\n",
    "events = graph.stream(\n",
    "    input=input,\n",
    "    config=config,\n",
    "    interrupt_before=[\"tools\"],\n",
    "    stream_mode=\"values\",\n",
    ")\n",
    "for event in events:\n",
    "    if \"messages\" in event:\n",
    "        event[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2c49b0e0",
   "metadata": {},
   "source": [
    "다음으로, 에이전트를 위한 도구 호출을 업데이트해 보겠습니다. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f88483e9",
   "metadata": {},
   "source": [
    "먼저 `Message ID` 를 가져옵니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "72d210f5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# config 를 복사\n",
    "config_copy = config.copy()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "2bac7050",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Message ID run-f78c69ba-6403-45dc-b355-28a2b326471e-0\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.messages import AIMessage\n",
    "\n",
    "# 스냅샷 상태 가져오기\n",
    "snapshot = graph.get_state(config)\n",
    "\n",
    "# messages 의 마지막 메시지 가져오기\n",
    "existing_message = snapshot.values[\"messages\"][-1]\n",
    "\n",
    "# 메시지 ID 출력\n",
    "print(\"Message ID\", existing_message.id)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ed080b7",
   "metadata": {},
   "source": [
    "마지막 메시지는 `tavily_web_search` 도구 호출과 관련된 메시지 입니다.\n",
    "\n",
    "주요 속성은 다음과 같습니다.\n",
    "\n",
    "- `name`: 도구의 이름\n",
    "- `args`: 검색 쿼리\n",
    "- `id`: 도구 호출 ID\n",
    "- `type`: 도구 호출 유형(tool_call)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "64c27714",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'name': 'tavily_web_search', 'args': {'query': 'LangGraph 소개 및 자료'}, 'id': 'call_xJVDxiPGNJcHmErUMrCEMxdF', 'type': 'tool_call'}\n"
     ]
    }
   ],
   "source": [
    "# 첫 번째 도구 호출 출력\n",
    "print(existing_message.tool_calls[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b2f3e04e",
   "metadata": {},
   "source": [
    "위의 속성 값 중 `args` 의 `query` 를 업데이트 해 보겠습니다.\n",
    "\n",
    "기존의 `existing_message` 를 복사하여 새로운 도구인 `new_tool_call` 을 생성합니다.\n",
    "\n",
    "`copy()` 메서드를 사용하여 복사하였기 때문에 모든 속성 값이 복사됩니다.\n",
    "\n",
    "그런 다음, `query` 매개변수에 원하는 **검색 쿼리** 를 입력합니다.\n",
    "\n",
    "**중요**\n",
    "\n",
    "- `id` 는 기존 메시지의 `id` 를 그대로 사용합니다. (`id` 가 달라지면 message 리듀서가 동작하여 메시지를 갱신하지 않고, 추가하게 됩니다.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "97b66610",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'name': 'tavily_web_search',\n",
       " 'args': {'query': 'LangGraph site:teddylee777.github.io'},\n",
       " 'id': 'call_xJVDxiPGNJcHmErUMrCEMxdF',\n",
       " 'type': 'tool_call'}"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# tool_calls 를 복사하여 새로운 도구 호출 생성\n",
    "new_tool_call = existing_message.tool_calls[0].copy()\n",
    "\n",
    "# 쿼리 매개변수 업데이트(갱신)\n",
    "new_tool_call[\"args\"] = {\"query\": \"LangGraph site:teddylee777.github.io\"}\n",
    "new_tool_call"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "8891040b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "run-f78c69ba-6403-45dc-b355-28a2b326471e-0\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  tavily_web_search (call_xJVDxiPGNJcHmErUMrCEMxdF)\n",
      " Call ID: call_xJVDxiPGNJcHmErUMrCEMxdF\n",
      "  Args:\n",
      "    query: LangGraph site:teddylee777.github.io\n"
     ]
    }
   ],
   "source": [
    "# AIMessage 생성\n",
    "new_message = AIMessage(\n",
    "    content=existing_message.content,\n",
    "    tool_calls=[new_tool_call],\n",
    "    # 중요! ID는 메시지를 상태에 추가하는 대신 교체하는 방법\n",
    "    id=existing_message.id,\n",
    ")\n",
    "\n",
    "print(new_message.id)\n",
    "\n",
    "# 수정한 메시지 출력\n",
    "new_message.pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3e18fa63",
   "metadata": {},
   "source": [
    "검색 쿼리가 갱신된 것을 확인할 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "7dbb6442",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'name': 'tavily_web_search', 'args': {'query': 'LangGraph site:teddylee777.github.io'}, 'id': 'call_xJVDxiPGNJcHmErUMrCEMxdF', 'type': 'tool_call'}\n",
      "\n",
      "Message ID run-f78c69ba-6403-45dc-b355-28a2b326471e-0\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'configurable': {'thread_id': '6c95af',\n",
       "  'checkpoint_ns': '',\n",
       "  'checkpoint_id': '1ef99e9c-a81a-63e6-8002-84fbc4bcb50a'}}"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 업데이트된 도구 호출 출력\n",
    "print(new_message.tool_calls[0])\n",
    "\n",
    "# 메시지 ID 출력\n",
    "print(\"\\nMessage ID\", new_message.id)\n",
    "\n",
    "# 상태 업데이트\n",
    "graph.update_state(config, {\"messages\": [new_message]})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2f791a61",
   "metadata": {},
   "source": [
    "업데이트된 마지막 message 의 `tool_calls` 를 확인합니다.\n",
    "\n",
    "- `args` 의 `query` 가 수정된 것을 확인할 수 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "f9dbecb3",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'name': 'tavily_web_search',\n",
       "  'args': {'query': 'LangGraph site:teddylee777.github.io'},\n",
       "  'id': 'call_xJVDxiPGNJcHmErUMrCEMxdF',\n",
       "  'type': 'tool_call'}]"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 마지막 메시지의 도구 호출 가져오기\n",
    "graph.get_state(config).values[\"messages\"][-1].tool_calls"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e0ae770e",
   "metadata": {},
   "source": [
    "기존 검색어인 **\"LangGraph\"** 대신 변경된 검색 쿼리인 **\"LangGraph site:teddylee777.github.io\"** 로 검색되는 것을 확인할 수 있습니다.\n",
    "\n",
    "기존 설정과 `None` 입력을 사용하여 그래프 이어서 스트리밍 합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "80be43c4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  tavily_web_search (call_xJVDxiPGNJcHmErUMrCEMxdF)\n",
      " Call ID: call_xJVDxiPGNJcHmErUMrCEMxdF\n",
      "  Args:\n",
      "    query: LangGraph site:teddylee777.github.io\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: tavily_web_search\n",
      "\n",
      "[\"<document><title>LangGraph - Multi-Agent Collaboration(다중 협업 ... - 테디노트</title><url>https://teddylee777.github.io/langgraph/langgraph-multi-agent-collaboration/</url><content>LangGraph 라이브러리가 출시하게 된 가장 큰 이유는 \\\\\\\"복잡한 문제들은 단일 AI 에이전트만으로 해결하기 어렵다\\\\\\\" 라는 문장에서 시작합니다. 이 글에서는 LangGraph의 다중 에이전트 협업을 통해 이러한 문제들을 효과적으로 해결하는 방법을 소개합니다.</content><raw>🔥알림🔥\\n① 테디노트 유튜브 -\\n구경하러 가기!\\n② LangChain 한국어 튜토리얼\\n바로가기 👀\\n③ 랭체인 노트 무료 전자책(wikidocs)\\n바로가기 🙌\\n④ RAG 비법노트 LangChain 강의오픈\\n바로가기 🙌\\n⑤ 서울대 PyTorch 딥러닝 강의\\n바로가기 🙌\\nLangGraph - Multi-Agent Collaboration(다중 협업 에이전트) 로 복잡한 테스크를 수행하는 LLM 어플리케이션 제작\\n2024년 01월 29일\\n26 분 소요\\nLangChain 에서 야심차게 LangGraph 를 새롭게 출시하였습니다. LangGraph 라이브러리가 출시하게 된 가장 큰 이유는 “복잡한 문제들은 단일 AI 에이전트만으로 해결하기 어렵다” 라는 문장에서 시작합니다.\\n이 글에서는 LangGraph의 다중 에이전트 협업을 통해 이러한 문제들을 효과적으로 해결하는 방법을 소개합니다. 특히, LangGraph를 활용한 새로운 접근법을 중점적으로 다룹니다.\\nReference\\n본 튜토리얼은 LangGraph 튜토리얼 노트북 파일을 참조하여 작성하였습니다.\\n실습파일\\n요약\\n주요내용\\n튜토리얼 영상\\n기본 다중 에이전트 협업\\n단일 에이전트는 일반적으로 단일 도메인 내에서 소수의 도구를 사용하여 효과적으로 작동할 수 있지만, gpt-4와 같은 강력한 모델을 사용하더라도 많은 도구를 사용하는 데에는 덜 효과적일 수 있습니다.\\n복잡한 작업에 접근하는 한 가지 방법은 ‘나누고 정복하기(divide-and-conquer)’ 접근법입니다. 각 작업이나 도메인에 대해 전문화된 에이전트를 만들고 작업을 올바른 ‘전문가’에게 라우팅합니다.\\n이 노트북은 Wu 등이 작성한 논문 AutoGen: 다중 에이전트 대화를 통한 차세대 LLM 애플리케이션 가능하게 하기에서 영감을 받아 LangGraph를 사용하여 이를 수행하는 한 가지 방법을 보여줍니다.\\n결과적으로 생성된 그래프는 다음 다이어그램과 같은 모습일 것입니다.\\n필요한 패키지를 설치합니다.\\npython-dotenv 라이브러리를 사용하여 환경 변수에서 토큰 정보를 로드합니다.\\n이 라이브러리는 .env 파일에 저장된 환경 변수를 읽어와서 Python 프로그램에서 사용할 수 있게 해줍니다. load_dotenv 함수를 호출하여 .env 파일의 내용을 로드합니다.\\n_set_if_undefined는 환경 변수가 설정되어 있지 않을 경우 사용자로부터 해당 환경 변수의 값을 입력받아 설정하는 역할을 합니다. 이 함수는 OPENAI_API_KEY, LANGCHAIN_API_KEY, TAVILY_API_KEY 세 개의 API 키에 대해 환경 변수가 설정되어 있는지 확인하고, 없을 경우 사용자에게 입력을 요청합니다.\\n또한, LangSmith 추적 기능을 활성화하고 프로젝트 이름을 ‘Multi-agent Collaboration’으로 설정합니다.\\n에이전트 생성\\ncreate_agent는 AI 에이전트를 생성하는 데 사용됩니다. 이 에이전트는 다른 AI 보조자들과 협력하여 제공된 도구들을 사용해 질문에 대한 답변을 진행합니다. 에이전트는 최종 답변을 할 수 있는 경우 응답 앞에 ‘FINAL ANSWER’라고 표시하여 팀이 작업을 멈출 수 있도록 합니다.\\ncreate_agent 함수는 llm, tools, system_message 세 개의 매개변수를 받으며, 각 도구의 이름을 포함한 프롬프트를 생성하여 에이전트에게 전달합니다.\\nformat_tool_to_openai_function 함수는 도구를 OpenAI 함수 형식으로 변환하는 데 사용되며, ChatPromptTemplate.from_messages는 에이전트에게 전달될 메시지 템플릿을 생성합니다. 마지막으로, 생성된 프롬프트는 llm.bind_functions을 통해 에이전트의 기능과 결합됩니다.\\nTool(도구) 정의\\n우리의 에이전트들이 미래에 사용할 몇 가지 도구들을 정의할 것입니다\\n함수 python_repl은 주어진 파이썬 코드를 실행하고 그 결과를 반환합니다. 이 함수는 tool 데코레이터로 장식되어 있으며, 사용자가 제공한 코드를 PythonREPL 인스턴스를 통해 실행합니다.\\n실행 결과는 표준 출력으로 반환되며, 실행 중 발생한 예외는 오류 메시지와 함께 반환됩니다. 사용자는 print(...) 함수를 사용하여 값을 출력할 수 있습니다.\\n이 함수는 차트 생성과 같은 작업에 사용될 수 있습니다.\\n그래프 생성\\n이제 우리는 도구를 정의하고 몇 가지 헬퍼 함수를 만들었으므로, 아래에서 개별 에이전트를 생성하고 LangGraph를 사용하여 서로 통신하는 방법을 알려줄 것입니다.\\n상태 정의\\n우리는 먼저 그래프의 상태를 정의합니다. 이것은 가장 최근의 발신자를 추적하는 키와 함께 메시지의 리스트일 것입니다.\\n이 코드는 langchain 라이브러리를 사용하여 AI 에이전트와 상호작용하는 그래프 기반의 시스템을 구축하기 위한 준비 작업을 포함하고 있습니다.\\nAgentState 클래스는 에이전트의 상태를 나타내며, messages는 BaseMessage 객체의 시퀀스로 구성되며, sender는 메시지를 보낸 사람의 식별자를 나타냅니다.\\nAnnotated와 operator.add를 사용하여 messages 필드에 메시지를 추가할 수 있는 기능을 제공합니다.\\n에이전트 노드 정의하기\\n이제 노드를 정의할 필요가 있습니다. 먼저, 에이전트들을 위한 노드를 정의합시다.\\n함수 agent_node는 주어진 에이전트의 상태를 처리하고, 결과 메시지와 발신자 정보를 포함하는 딕셔너리를 반환합니다. FunctionMessage 인스턴스인 경우에는 특별한 처리를 하지 않고, 그렇지 않은 경우에는 HumanMessage 인스턴스로 변환합니다.\\nChatOpenAI 클래스의 인스턴스 llm은 모델 gpt-4-1106-preview를 사용하여 생성됩니다.\\nresearch_agent와 chart_agent는 각각 연구자와 차트 생성기를 위한 에이전트를 생성하며, 이들은 llm을 사용하고 특정 도구(tavily_tool, python_repl)와 시스템 메시지를 설정합니다. research_node와 chart_node는 functools.partial을 사용하여 agent_node 함수에 필요한 인자를 미리 설정한 콜백 함수를 생성합니다.\\nTool Node (도구 노드) 정의\\n우리는 이제 도구를 실행하는 노드를 정의합니다\\ntool_node는 상태 객체를 받아 마지막 메시지에서 도구 호출을 추출하고, 해당 도구를 실행한 후 결과를 FunctionMessage 형태로 반환합니다.\\n이 함수는 ToolExecutor 인스턴스를 사용하여 도구를 실행하며, 실행 결과는 새로운 메시지 리스트에 추가되어 반환됩니다.\\n단일 인자 입력은 값으로 직접 전달되며, 복수 인자의 경우 ToolInvocation 객체를 통해 전달됩니다.\\n에지 로직 정의\\n에이전트의 결과에 따라 무엇을 할지 결정하는 데 필요한 일부 에지 로직을 정의할 수 있습니다.\\nrouter는 상태 정보를 받아서 다음 단계를 결정합니다. 상태 정보에서 마지막 메시지를 확인하고, 이 메시지에 특정 키워드가 포함되어 있는지에 따라 다음 단계를 반환합니다.\\n만약 마지막 메시지의 additional_kwargs에 function_call이 포함되어 있다면, 이전 에이전트가 도구를 호출한 것으로 간주하고 \\\"call_tool\\\"을 반환합니다. 만약 마지막 메시지의 내용에 \\\"FINAL ANSWER\\\"가 포함되어 있다면, 작업이 완료되었다고 판단하고 \\\"end\\\"를 반환합니다. 그렇지 않으면 작업을 계속 진행해야 하므로 \\\"continue\\\"를 반환합니다.\\n그래프 정의하기\\n이제 모든 것을 종합하여 그래프를 정의할 수 있습니다!\\nStateGraph 클래스를 사용하여 상태 기반 워크플로우를 구성합니다.\\nAgentState를 기반으로 StateGraph 객체를 생성하고, add_node 메서드를 사용하여 여러 노드(Researcher, Chart Generator, call_tool)를 추가합니다.\\nadd_conditional_edges 메서드를 통해 조건부 엣지를 추가하여, 각 노드가 특정 조건에 따라 다른 노드로 이동할 수 있도록 합니다. 예를 들어, Researcher 노드는 router 함수의 결과에 따라 Chart Generator 또는 call_tool로 이동하거나 워크플로우를 종료할 수 있습니다. call_tool 노드는 sender 필드를 기반으로 원래 호출한 노드로 돌아갑니다.\\n마지막으로 set_entry_point 메서드로 시작점을 Researcher로 설정하고, compile 메서드로 워크플로우 그래프를 컴파일합니다.\\n호출\\n그래프가 생성되었으니 이제 호출할 수 있습니다! 몇 가지 통계를 차트로 나타내도록 해봅시다.\\n이 코드는 graph 객체의 stream 메소드를 사용하여 데이터를 스트리밍합니다.\\nHumanMessage 객체를 사용하여 대한민국의 2018년부터 2022년까지의 출산율 데이터를 요청하고, 해당 데이터에 대한 그래프를 그리도록 요청합니다. 또한, 코드 실행이 완료되면 종료하도록 메시지에 포함되어 있습니다.\\n스트림은 최대 200번의 재귀 호출로 제한됩니다.\\n라이선스\\n태그:\\nAutoGen,\\nLangChain,\\nLangGraph,\\nLLM,\\nPython,\\n그래프 기반 시스템,\\n노드,\\n다중 에이전트,\\n데이터 스트리밍,\\n딥러닝,\\n랭체인,\\n머신러닝,\\n멀티에이전트,\\n상태 정의,\\n에지 로직,\\n협업,\\n호출\\n카테고리:\\nlanggraph\\n업데이트: 2024년 01월 29일\\n참고\\npoetry 의 거의 모든것 (튜토리얼)\\n2024년 03월 30일\\n5 분 소요\\nPython 개발에 있어서 poetry는 매우 강력한 도구로, 프로젝트의 의존성 관리와 패키지 배포를 간소화하는 데 큰 도움을 줍니다. 지금부터 poetry 활용 튜토리얼을 살펴 보겠습니다.\\nLangGraph Retrieval Agent를 활용한 동적 문서 검색 및 처리\\n2024년 03월 06일\\n10 분 소요\\nLangGraph Retrieval Agent는 언어 처리, AI 모델 통합, 데이터베이스 관리, 그래프 기반 데이터 처리 등 다양한 기능을 제공하여 언어 기반 AI 애플리케이션 개발에 필수적인 도구입니다.\\n[Assistants API] Code Interpreter, Retrieval, Functions 활용법\\n2024년 02월 13일\\n35 분 소요\\nOpenAI의 새로운 Assistants API는 대화와 더불어 강력한 도구 접근성을 제공합니다. 본 튜토리얼은 OpenAI Assistants API를 활용하는 내용을 다룹니다. 특히, Assistant API 가 제공하는 도구인 Code Interpreter, Retrieval...\\n[LangChain] 에이전트(Agent)와 도구(tools)를 활용한 지능형 검색 시스템 구축 가이드\\n2024년 02월 09일\\n41 분 소요\\n이 글에서는 LangChain 의 Agent 프레임워크를 활용하여 복잡한 검색과 데이터 처리 작업을 수행하는 방법을 소개합니다. LangSmith 를 사용하여 Agent의 추론 단계를 추적합니다. Agent가 활용할 검색 도구(Tavily Search), PDF 기반 검색 리트리버...</raw></document>\", \"<document><title>랭체인(langchain) + PDF 문서요약, Map-Reduce (7) - 테디노트</title><url>https://teddylee777.github.io/langchain/langchain-tutorial-07/</url><content>LangGraph Retrieval Agent를 활용한 동적 문서 검색 및 처리 2024년 03월 06일 10 분 소요 LangGraph Retrieval Agent는 언어 처리, AI 모델 통합, 데이터베이스 관리, 그래프 기반 데이터 처리 등 다양한 기능을 제공하여 언어 기반 AI 애플리케이션 개발에 필수적인 도구입니다.</content><raw>🔥알림🔥\\n① 테디노트 유튜브 -\\n구경하러 가기!\\n② LangChain 한국어 튜토리얼\\n바로가기 👀\\n③ 랭체인 노트 무료 전자책(wikidocs)\\n바로가기 🙌\\n랭체인(langchain) + PDF 문서요약, Map-Reduce (7)\\n2023년 10월 06일\\n11 분 소요\\n이번 포스팅에서는 랭체인(LangChain) 을 활용하여 PDF 문서를 로드하고, 문서의 내용을 요약 하는 방법에 대해 알아보겠습니다.\\n이번 튜토리얼에서는 langchain 의 PyPDFLoader 를 활용한 PDF 문서의 텍스트 데이터를 불러오는 방법에 대해 다룹니다.\\n또한, 긴 PDF 문서를 쪼개서 요약 및 통합하는 Map-Reduce 방식의 요약 방식에 대해 깊게 다루도록 하겠습니다. Map-Reduce 방식은 LLM의 제한적인 토큰 사이즈를 입력으로 받는 구조적 문제를 해결하는 방법론이기 때문에 비단 요약 뿐만아니라 그 밖에 확장된 테스크에도 적용이 가능합니다.\\n✔️ (이전글) LangChain 튜토리얼\\n🌱 환경설정\\n🔥 PDF 기반 문서요약\\n✔️ 문서요약 방식\\nPDF 의 문서를 요약하는 방식에는 문서의 크기에 따라 2가지 방식으로 나누어 볼 수 있습니다.\\nStuff: 전체 문서의 내용을 모두 프롬프트의 입력으로 대입합니다. 전체 문서의 내용이 LLM 모델의 최대토큰 허용 크기보다 작은 경우, 이 방식을 선택할 수 있습니다.\\nMap-Reduce: 전체 문서의 내용을 쪼개서 여러의 부분 세트로 나눈 뒤, 나눈 내용을 프롬프트의 입력을 나누어 대입합니다. 예를 들어, PDF 문서가 100장으로 구성이 되어 있고, 100장 분량의 내용을 프롬프트의 입력으로 한 번에 넣을 수 없으므로, 20장씩 5개의 세트로 나눈 뒤 5번의 프롬프트 입력 후 결과로 나온 5개의 요약본을 통합하는 작업을 진행합니다.\\nStuff 방식은 코드가 간결하지만, 현실적으로 잘 사용하게 되지 않습니다. 일반적인 PDF 문서의 전체 길이가 LLM 모델의 최대토큰 허용 크기보다 큰 경우가 대다수의 경우이기 때문입니다. 따라서, 이번 튜토리얼에서는 Map-Reduce 방식에 대해 자세히 다루며, Stuff 방식은 여기 에서 참고하실 수 있습니다.\\n📍 흐름\\n흐름도\\n위의 흐름도를 보면서 PDF 문서를 요약하는 흐름에 대해서 정리해 보겠습니다.\\n① 문서 로드\\n먼저, langchain 의 PDF 문서의 로드를 도와주는 PDF Loader 를 활용하여 문서를 로드합니다. langchain에서는 다양한 PDF Loader 를 제공하며, 대표적인 예시로는 PyPDFLoader, PyMuPDFLoader, UnstructuredPDFLoader 등이 있습니다.\\n여기서 문서를 로드한다는 의미는 PDF 문서의 내용을 긁어와 String 형태로 가져오는 것을 의미하며 정확하게는 Document 객체안에 page_content 속성으로 로드합니다. PDF 로드시 각종 메타데이터로 함께 로드합니다.\\nPyPDFLoader 사용하여 PDF를 로드하고, 각 문서가 페이지 내용과 페이지 번호를 포함한 메타데이터를 포함하는 문서의 배열로 만듭니다.\\n② 문서 분할\\n문서의 내용이 긴 경우, 문서 전체의 내용을 프롬프트의 입력으로 넣을 수 없으므로 문서를 분할하는 작업을 선행합니다.\\n문서 분할은 토큰갯수에 따라 분할할 수 있으며, 대표적으로 많이 사용되는 모듈은 CharacterTextSplitter, RecursiveCharacterTextSplitter 등이 있습니다.\\n아래는 가장 단순한 방법인 CharacterTextSplitter 를 활용하여 문서를 분할한 예시 입니다.\\n이 방법은 문자(기본적으로 “\\\\n\\\\n”)를 기반으로 분할하며, 문자의 수(chunk_size)로 길이를 측정합니다.\\n텍스트의 분할 방식: 단일 문자 기준\\nchunk_size 의 측정 방식: 문자의 수로 측정\\n③ 분할된 각 문서에 대한 요약 실행\\n분할된 문서에 요약(summarization) 과 같은 테스크를 수행하는 것을 Map, 각 문서의 요약본을 하나로 통합하는 작업을 Reduce 라고 부릅니다. 즉, Map-Reduce 방식은 문서를 분할 - 요약 - 통합 을 수행하게 됩니다.\\n그 중 Map 단계에서 수행할 프롬프트를 템플릿으로 정의하고 chain 을 생성해 보겠습니다.\\n④ 각 문서의 요약본에 대한 통합\\nReduce 단계는 이전 단계인 Map 단계에서 분할된 문서에 대한 요약본을 통합처리 하는 역할을 수행합니다.\\nReduce 단계에서는 통합처리시 필요한 프롬프트를 정의합니다. 통합처리시 단순하게 전체 요약본 테스크를 수행할 수 도 있고, 요약본을 토대로 보고서, 이메일, 뉴스레터, 독서감상문 등의 다양한 테스크를 수행하도록 할 수 있습니다.\\n아래는 통합 요약본을 작성하는 프롬프트 예시입니다.\\ncombine_documents_chain 은 Reduce 작업을 수행하는 체인입니다.\\nMap 단계에서 완성된 분할된 요약본이 StuffDocumentsChain 의 doc_summaries 입력으로 주입됩니다.\\nReduceDocumentsChain 은 주입된 입력 값을 통합하는 작업을 수행합니다.\\n⑤ 통합체인(Combined Chain) 생성\\n통합체인 생성 단계에서는 이전에 정의한 Map 체인과 Reduce 체인을 연결하고, MapReduceDocumentsChain 객체를 통해 통합하는 단계입니다.\\n🔥 전체코드\\n이전 단계의 결과물인 문서의 통합요약본을 바탕으로 다음과 같은 추가 Task를 수행할 수 있습니다. 아래는 독서감상문 작성의 예시입니다.\\n태그:\\nChatGPT,\\nChatOpenAI,\\nGPT3.5,\\nGPT4,\\nlangchain,\\nlangchain tutorial,\\nOpenAI,\\nPDF,\\n뉴스레터,\\n독서감상문,\\n랭체인,\\n랭체인 튜토리얼,\\n문서요약,\\n크롤링,\\n회의록\\n카테고리:\\nlangchain\\n업데이트: 2023년 10월 06일\\n참고\\npoetry 의 거의 모든것 (튜토리얼)\\n2024년 03월 30일\\n5 분 소요\\nPython 개발에 있어서 poetry는 매우 강력한 도구로, 프로젝트의 의존성 관리와 패키지 배포를 간소화하는 데 큰 도움을 줍니다. 지금부터 poetry 활용 튜토리얼을 살펴 보겠습니다.\\nLangGraph Retrieval Agent를 활용한 동적 문서 검색 및 처리\\n2024년 03월 06일\\n10 분 소요\\nLangGraph Retrieval Agent는 언어 처리, AI 모델 통합, 데이터베이스 관리, 그래프 기반 데이터 처리 등 다양한 기능을 제공하여 언어 기반 AI 애플리케이션 개발에 필수적인 도구입니다.\\n[Assistants API] Code Interpreter, Retrieval, Functions 활용법\\n2024년 02월 13일\\n35 분 소요\\nOpenAI의 새로운 Assistants API는 대화와 더불어 강력한 도구 접근성을 제공합니다. 본 튜토리얼은 OpenAI Assistants API를 활용하는 내용을 다룹니다. 특히, Assistant API 가 제공하는 도구인 Code Interpreter, Retrieval...\\n[LangChain] 에이전트(Agent)와 도구(tools)를 활용한 지능형 검색 시스템 구축 가이드\\n2024년 02월 09일\\n41 분 소요\\n이 글에서는 LangChain 의 Agent 프레임워크를 활용하여 복잡한 검색과 데이터 처리 작업을 수행하는 방법을 소개합니다. LangSmith 를 사용하여 Agent의 추론 단계를 추적합니다. Agent가 활용할 검색 도구(Tavily Search), PDF 기반 검색 리트리버...</raw></document>\", \"<document><title>랭체인(langchain) + 정형데이터(CSV, Excel) - ChatGPT 기반 데이터분석 (4) - 테디노트</title><url>https://teddylee777.github.io/langchain/langchain-tutorial-04/</url><content>LangGraph Retrieval Agent를 활용한 동적 문서 검색 및 처리 2024년 03월 06일 10 분 소요 LangGraph Retrieval Agent는 언어 처리, AI 모델 통합, 데이터베이스 관리, 그래프 기반 데이터 처리 등 다양한 기능을 제공하여 언어 기반 AI 애플리케이션 개발에 필수적인 도구입니다.</content><raw>🔥알림🔥\\n① 테디노트 유튜브 -\\n구경하러 가기!\\n② LangChain 한국어 튜토리얼\\n바로가기 👀\\n③ 랭체인 노트 무료 전자책(wikidocs)\\n바로가기 🙌\\n④ RAG 비법노트 LangChain 강의오픈\\n바로가기 🙌\\n⑤ 서울대 PyTorch 딥러닝 강의\\n바로가기 🙌\\n랭체인(langchain) + 정형데이터(CSV, Excel) - ChatGPT 기반 데이터분석 (4)\\n2023년 10월 02일\\n4 분 소요\\n이번 포스팅에서는 랭체인(LangChain) 을 활용하여 정형데이터(CSV, Excel) 에 대한 ChatGPT 기반 질의응답을 통해 데이터 분석하는 방법 에 대해 알아보겠습니다.\\n이번 튜토리얼에서는 langchain 의 create_pandas_dataframe_agent() 을 통해 에이전트를 생성한 뒤, 생성된 에이전트에 자연어로 데이터에 대한 질의 응답 을 통해 원하는 분석 결과를 도출하는 방법에 대해 다루겠습니다.\\n한가지 흥미로운 사실은 자연어로 에이전트에 질문하면, 에이전트가 이를 pandas 문법을 변환하여 코드를 실행한 뒤, 결과를 다시 자연어로 반환해 준다는 점입니다. 에이전트가 답변을 도출하는 과정에서 실행한 pandas 코드도 확인 할 수 있습니다.\\n✔️ (이전글) LangChain 튜토리얼\\n🌱 환경설정\\n라이브러리 설치\\nOPENAI API KEY 를 설정합니다.\\n🔥 데이터 로드\\npandas를 활용하여 csv 파일을 DataFrame 으로 로드합니다.\\n🔥 Pandas DataFrame Agent\\n🔥 2개 이상의 DataFrame\\n2개 이상의 데이터프레임에 기반한 LLM 기반 질의를 할 수 있습니다. 2개 이상의 데이터프레임 입력시 [] 로 묶어주면 됩니다.\\n태그:\\nChatGPT,\\nChatOpenAI,\\ncsv,\\nexcel,\\nGPT3.5,\\nGPT4,\\nlangchain,\\nlangchain tutorial,\\nOpenAI,\\n데이터분석,\\n랭체인,\\n랭체인 튜토리얼\\n카테고리:\\nlangchain\\n업데이트: 2023년 10월 02일\\n참고\\npoetry 의 거의 모든것 (튜토리얼)\\n2024년 03월 30일\\n5 분 소요\\nPython 개발에 있어서 poetry는 매우 강력한 도구로, 프로젝트의 의존성 관리와 패키지 배포를 간소화하는 데 큰 도움을 줍니다. 지금부터 poetry 활용 튜토리얼을 살펴 보겠습니다.\\nLangGraph Retrieval Agent를 활용한 동적 문서 검색 및 처리\\n2024년 03월 06일\\n10 분 소요\\nLangGraph Retrieval Agent는 언어 처리, AI 모델 통합, 데이터베이스 관리, 그래프 기반 데이터 처리 등 다양한 기능을 제공하여 언어 기반 AI 애플리케이션 개발에 필수적인 도구입니다.\\n[Assistants API] Code Interpreter, Retrieval, Functions 활용법\\n2024년 02월 13일\\n35 분 소요\\nOpenAI의 새로운 Assistants API는 대화와 더불어 강력한 도구 접근성을 제공합니다. 본 튜토리얼은 OpenAI Assistants API를 활용하는 내용을 다룹니다. 특히, Assistant API 가 제공하는 도구인 Code Interpreter, Retrieval...\\n[LangChain] 에이전트(Agent)와 도구(tools)를 활용한 지능형 검색 시스템 구축 가이드\\n2024년 02월 09일\\n41 분 소요\\n이 글에서는 LangChain 의 Agent 프레임워크를 활용하여 복잡한 검색과 데이터 처리 작업을 수행하는 방법을 소개합니다. LangSmith 를 사용하여 Agent의 추론 단계를 추적합니다. Agent가 활용할 검색 도구(Tavily Search), PDF 기반 검색 리트리버...</raw></document>\"]\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "LangGraph에 대해 더 알아보실 수 있는 유용한 자료는 다음과 같습니다:\n",
      "\n",
      "1. **[LangGraph - Multi-Agent Collaboration](https://teddylee777.github.io/langgraph/langgraph-multi-agent-collaboration/)**  \n",
      "   이 글에서는 LangGraph 라이브러리와 다중 에이전트 협업을 통해 복잡한 문제를 해결하는 방법을 소개합니다. 특히, 전문화된 에이전트를 만들어 작업을 효과적으로 분배하는 접근법에 대해 설명합니다.\n",
      "\n",
      "2. **[LangGraph Retrieval Agent를 활용한 동적 문서 검색 및 처리](https://teddylee777.github.io/langchain/langchain-tutorial-07/)**  \n",
      "   이 포스팅에서는 LangGraph Retrieval Agent를 사용하여 언어 처리 및 AI 모델 통합, 데이터베이스 관리 등 다양한 기능을 제공하는 방법에 대해 다룹니다.\n",
      "\n",
      "3. **[랭체인 + 정형데이터](https://teddylee777.github.io/langchain/langchain-tutorial-04/)**  \n",
      "   이 글에서는 LangChain을 활용하여 정형 데이터(CSV, Excel)에 대한 ChatGPT 기반 질의응답을 통해 데이터 분석하는 방법을 소개합니다. 에이전트를 생성하고 자연어로 질문하는 과정에 대한 설명이 포함되어 있습니다.\n",
      "\n",
      "이 자료들을 통해 LangGraph의 기능 및 활용 방법에 대해 깊이 있게 이해하실 수 있을 것입니다.\n"
     ]
    }
   ],
   "source": [
    "# 그래프 스트림에서 이벤트 수신\n",
    "events = graph.stream(None, config, stream_mode=\"values\")\n",
    "\n",
    "# 각 이벤트에 대한 처리\n",
    "for event in events:\n",
    "    # 메시지가 있는 경우 마지막 메시지 출력\n",
    "    if \"messages\" in event:\n",
    "        event[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "df57c7da",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "내가 지금까지 배운 내용에 대해서 매우 친절하고 정성스럽게 한국어로 답변해줘! 출처를 반드시 포함해줘!\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "물론입니다! 지금까지 배운 내용을 친절하고 정성스럽게 요약해드리겠습니다.\n",
      "\n",
      "---\n",
      "\n",
      "### LangGraph 개요\n",
      "\n",
      "LangGraph는 복잡한 문제를 해결하기 위해 다중 에이전트 협업을 가능하게 하는 라이브러리입니다. 이 라이브러리는 단일 AI 에이전트로는 해결하기 어려운 문제들을 나누고 정복하는 방식으로 접근합니다. 즉, 각 작업이나 도메인에 특화된 에이전트를 만들고, 이 에이전트들이 팀워크를 통해 문제를 해결하도록 합니다.\n",
      "\n",
      "### 주요 기능\n",
      "\n",
      "1. **다중 에이전트 협업**:\n",
      "   - LangGraph는 여러 개의 AI 에이전트가 서로 협력하여 작업을 수행하도록 설계되었습니다. 각 에이전트는 특정 도메인이나 작업에 최적화되어 있어, 복잡한 문제를 효과적으로 해결할 수 있습니다.\n",
      "\n",
      "2. **전문화된 에이전트 생성**:\n",
      "   - 복잡한 작업에 대해 전문화된 에이전트를 만들어 작업을 올바른 전문가에게 라우팅합니다. 이를 통해 각 에이전트가 최적의 성능을 발휘할 수 있습니다.\n",
      "\n",
      "3. **상태 기반 워크플로우**:\n",
      "   - LangGraph는 상태 그래프를 사용하여 에이전트 간의 상호작용을 관리합니다. 각 에이전트의 상태를 정의하고, 메시지의 흐름을 추적하여 작업을 진행합니다.\n",
      "\n",
      "4. **도구와 기능 통합**:\n",
      "   - LangGraph는 다양한 도구와 기능을 통합하여 사용할 수 있습니다. 예를 들어, Python REPL 도구를 통해 코드를 실행하거나, 특정 작업을 수행할 수 있는 기능을 포함합니다.\n",
      "\n",
      "### 활용 사례\n",
      "\n",
      "- **문서 검색 및 처리**: LangGraph Retrieval Agent를 사용하여 언어 처리 및 데이터베이스 관리 등을 통해 동적인 문서 검색과 처리를 수행할 수 있습니다.\n",
      "- **정형 데이터 분석**: CSV 또는 Excel 파일에 대한 질의응답을 통해 데이터 분석을 수행할 수 있으며, 자연어로 질문하면 에이전트가 이를 처리하여 결과를 반환합니다.\n",
      "\n",
      "### 출처\n",
      "- [LangGraph - Multi-Agent Collaboration](https://teddylee777.github.io/langgraph/langgraph-multi-agent-collaboration/)\n",
      "- [LangGraph Retrieval Agent를 활용한 동적 문서 검색 및 처리](https://teddylee777.github.io/langchain/langchain-tutorial-07/)\n",
      "- [랭체인 + 정형데이터](https://teddylee777.github.io/langchain/langchain-tutorial-04/)\n",
      "\n",
      "---\n",
      "\n",
      "이렇게 요약된 내용이 도움이 되길 바랍니다! 추가적인 질문이 있으시면 언제든지 말씀해 주세요.\n"
     ]
    }
   ],
   "source": [
    "# 이벤트 스트림 생성\n",
    "events = graph.stream(\n",
    "    {\n",
    "        \"messages\": (\n",
    "            \"user\",\n",
    "            \"내가 지금까지 배운 내용에 대해서 매우 친절하고 정성스럽게 한국어로 답변해줘! 출처를 반드시 포함해줘!\",\n",
    "        )\n",
    "    },\n",
    "    config,\n",
    "    stream_mode=\"values\",\n",
    ")\n",
    "\n",
    "# 메시지 이벤트 처리\n",
    "for event in events:\n",
    "    if \"messages\" in event:\n",
    "        # 마지막 메시지 출력\n",
    "        event[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "40da8a3c",
   "metadata": {},
   "source": [
    "최종 상태에서 `messages` 의 마지막 메시지를 확인합니다. (이는 곧 최종 응답 메시지입니다.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "f0e4a062",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "물론입니다! 지금까지 배운 내용을 친절하고 정성스럽게 요약해드리겠습니다.\n",
      "\n",
      "---\n",
      "\n",
      "### LangGraph 개요\n",
      "\n",
      "LangGraph는 복잡한 문제를 해결하기 위해 다중 에이전트 협업을 가능하게 하는 라이브러리입니다. 이 라이브러리는 단일 AI 에이전트로는 해결하기 어려운 문제들을 나누고 정복하는 방식으로 접근합니다. 즉, 각 작업이나 도메인에 특화된 에이전트를 만들고, 이 에이전트들이 팀워크를 통해 문제를 해결하도록 합니다.\n",
      "\n",
      "### 주요 기능\n",
      "\n",
      "1. **다중 에이전트 협업**:\n",
      "   - LangGraph는 여러 개의 AI 에이전트가 서로 협력하여 작업을 수행하도록 설계되었습니다. 각 에이전트는 특정 도메인이나 작업에 최적화되어 있어, 복잡한 문제를 효과적으로 해결할 수 있습니다.\n",
      "\n",
      "2. **전문화된 에이전트 생성**:\n",
      "   - 복잡한 작업에 대해 전문화된 에이전트를 만들어 작업을 올바른 전문가에게 라우팅합니다. 이를 통해 각 에이전트가 최적의 성능을 발휘할 수 있습니다.\n",
      "\n",
      "3. **상태 기반 워크플로우**:\n",
      "   - LangGraph는 상태 그래프를 사용하여 에이전트 간의 상호작용을 관리합니다. 각 에이전트의 상태를 정의하고, 메시지의 흐름을 추적하여 작업을 진행합니다.\n",
      "\n",
      "4. **도구와 기능 통합**:\n",
      "   - LangGraph는 다양한 도구와 기능을 통합하여 사용할 수 있습니다. 예를 들어, Python REPL 도구를 통해 코드를 실행하거나, 특정 작업을 수행할 수 있는 기능을 포함합니다.\n",
      "\n",
      "### 활용 사례\n",
      "\n",
      "- **문서 검색 및 처리**: LangGraph Retrieval Agent를 사용하여 언어 처리 및 데이터베이스 관리 등을 통해 동적인 문서 검색과 처리를 수행할 수 있습니다.\n",
      "- **정형 데이터 분석**: CSV 또는 Excel 파일에 대한 질의응답을 통해 데이터 분석을 수행할 수 있으며, 자연어로 질문하면 에이전트가 이를 처리하여 결과를 반환합니다.\n",
      "\n",
      "### 출처\n",
      "- [LangGraph - Multi-Agent Collaboration](https://teddylee777.github.io/langgraph/langgraph-multi-agent-collaboration/)\n",
      "- [LangGraph Retrieval Agent를 활용한 동적 문서 검색 및 처리](https://teddylee777.github.io/langchain/langchain-tutorial-07/)\n",
      "- [랭체인 + 정형데이터](https://teddylee777.github.io/langchain/langchain-tutorial-04/)\n",
      "\n",
      "---\n",
      "\n",
      "이렇게 요약된 내용이 도움이 되길 바랍니다! 추가적인 질문이 있으시면 언제든지 말씀해 주세요.\n"
     ]
    }
   ],
   "source": [
    "graph.get_state(config).values[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21173667",
   "metadata": {},
   "source": [
    "## 지난 스냅샷의 결과 수정 및 Replay\n",
    "\n",
    "이번에는 지난 스냅샷의 결과를 수정하여 Replay 하는 방법을 살펴보겠습니다.\n",
    "\n",
    "지난 스냅샷을 확인 후 특정 노드로 되돌아가, **상태(State) 를 수정한 뒤 해당 노드부터 다시 진행**합니다.\n",
    "\n",
    "이를 Replay 라고 합니다.\n",
    "\n",
    "먼저 지난 스냅샷의 상태를 가져옵니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "3eba0794",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "run-55afc5ee-6d63-4b11-b2a7-2835553e09d6-0\n",
      "메시지 수:  6 다음 노드:  ()\n",
      "--------------------------------------------------------------------------------\n",
      "d45abb6b-b2c4-40f8-86ce-1038fc289d0f\n",
      "메시지 수:  5 다음 노드:  ('chatbot',)\n",
      "--------------------------------------------------------------------------------\n",
      "run-af0f4be0-8950-4c29-b996-93e9332ca1d9-0\n",
      "메시지 수:  4 다음 노드:  ('__start__',)\n",
      "--------------------------------------------------------------------------------\n",
      "run-af0f4be0-8950-4c29-b996-93e9332ca1d9-0\n",
      "메시지 수:  4 다음 노드:  ()\n",
      "--------------------------------------------------------------------------------\n",
      "b599532c-fea7-4dac-918a-787264946d9b\n",
      "메시지 수:  3 다음 노드:  ('chatbot',)\n",
      "--------------------------------------------------------------------------------\n",
      "run-f78c69ba-6403-45dc-b355-28a2b326471e-0\n",
      "메시지 수:  2 다음 노드:  ('tools',)\n",
      "--------------------------------------------------------------------------------\n",
      "run-f78c69ba-6403-45dc-b355-28a2b326471e-0\n",
      "메시지 수:  2 다음 노드:  ('tools',)\n",
      "--------------------------------------------------------------------------------\n",
      "a08330c9-456f-4cf3-b155-194afa04a353\n",
      "메시지 수:  1 다음 노드:  ('chatbot',)\n",
      "--------------------------------------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "to_replay_state = None\n",
    "\n",
    "# 상태 기록 가져오기\n",
    "for state in graph.get_state_history(config):\n",
    "\n",
    "    messages = state.values[\"messages\"]\n",
    "\n",
    "    if len(messages) > 0:\n",
    "        print(state.values[\"messages\"][-1].id)\n",
    "        # 메시지 수 및 다음 상태 출력\n",
    "        print(\"메시지 수: \", len(state.values[\"messages\"]), \"다음 노드: \", state.next)\n",
    "        print(\"-\" * 80)\n",
    "        # 특정 상태 선택 기준: 채팅 메시지 수\n",
    "        if len(state.values[\"messages\"]) == 2:\n",
    "            # 특정 메시지 ID 선택\n",
    "            to_replay_state = state"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23cadcca",
   "metadata": {},
   "source": [
    "선택한 메시지의 내용을 확인합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "0463ea52",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "    \u001b[93mcontent\u001b[0m: \"\"\n",
      "    \u001b[93madditional_kwargs\u001b[0m:\n",
      "        \u001b[94mtool_calls\u001b[0m:\n",
      "            \u001b[94mindex [0]\u001b[0m\n",
      "                \u001b[92mid\u001b[0m: \"call_xJVDxiPGNJcHmErUMrCEMxdF\"\n",
      "                \u001b[92mfunction\u001b[0m: {\"arguments\": \"{\"query\":\"LangGraph 소개 및 자료\"}\", \"name\": \"tavily_web_search\"}\n",
      "                \u001b[92mtype\u001b[0m: \"function\"\n",
      "        \u001b[94mrefusal\u001b[0m: None\n",
      "    \u001b[93mresponse_metadata\u001b[0m:\n",
      "        \u001b[94mtoken_usage\u001b[0m:\n",
      "            \u001b[95mcompletion_tokens\u001b[0m: 21\n",
      "            \u001b[95mprompt_tokens\u001b[0m: 97\n",
      "            \u001b[95mtotal_tokens\u001b[0m: 118\n",
      "            \u001b[95mcompletion_tokens_details\u001b[0m: {\"audio_tokens\": None, \"reasoning_tokens\": 0}\n",
      "            \u001b[95mprompt_tokens_details\u001b[0m: {\"audio_tokens\": None, \"cached_tokens\": 0}\n",
      "        \u001b[94mmodel_name\u001b[0m: \"gpt-4o-mini-2024-07-18\"\n",
      "        \u001b[94msystem_fingerprint\u001b[0m: \"fp_0ba0d124f1\"\n",
      "        \u001b[94mfinish_reason\u001b[0m: \"tool_calls\"\n",
      "        \u001b[94mlogprobs\u001b[0m: None\n",
      "    \u001b[93mtype\u001b[0m: \"ai\"\n",
      "    \u001b[93mname\u001b[0m: None\n",
      "    \u001b[93mid\u001b[0m: \"run-f78c69ba-6403-45dc-b355-28a2b326471e-0\"\n",
      "    \u001b[93mexample\u001b[0m: False\n",
      "    \u001b[93mtool_calls\u001b[0m:\n",
      "        \u001b[93mindex [0]\u001b[0m\n",
      "            \u001b[95mname\u001b[0m: \"tavily_web_search\"\n",
      "            \u001b[95margs\u001b[0m: {\"query\": \"LangGraph 소개 및 자료\"}\n",
      "            \u001b[95mid\u001b[0m: \"call_xJVDxiPGNJcHmErUMrCEMxdF\"\n",
      "            \u001b[95mtype\u001b[0m: \"tool_call\"\n",
      "    \u001b[93minvalid_tool_calls\u001b[0m:\n",
      "    \u001b[93musage_metadata\u001b[0m:\n",
      "        \u001b[94minput_tokens\u001b[0m: 97\n",
      "        \u001b[94moutput_tokens\u001b[0m: 21\n",
      "        \u001b[94mtotal_tokens\u001b[0m: 118\n",
      "        \u001b[94minput_token_details\u001b[0m: {\"cache_read\": 0}\n",
      "        \u001b[94moutput_token_details\u001b[0m: {\"reasoning\": 0}\n"
     ]
    }
   ],
   "source": [
    "from langchain_teddynote.messages import display_message_tree\n",
    "\n",
    "# 선택한 메시지 가져오기\n",
    "existing_message = to_replay_state.values[\"messages\"][-1]\n",
    "\n",
    "# 메시지 트리 출력\n",
    "display_message_tree(existing_message)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "770063e4",
   "metadata": {},
   "source": [
    "검색 쿼리를 업데이트 후 반영이 됐는지 확인합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "7dbc1f15",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'name': 'tavily_web_search',\n",
       " 'args': {'query': 'LangGraph human-in-the-loop workflow site:reddit.com'},\n",
       " 'id': 'call_xJVDxiPGNJcHmErUMrCEMxdF',\n",
       " 'type': 'tool_call'}"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tool_call = existing_message.tool_calls[0].copy()\n",
    "tool_call[\"args\"] = {\"query\": \"LangGraph human-in-the-loop workflow site:reddit.com\"}\n",
    "tool_call"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20a66f6d",
   "metadata": {},
   "source": [
    "업데이트된 AIMessage 를 생성합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "2656f8db",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'query': 'LangGraph human-in-the-loop workflow site:reddit.com'}"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# AIMessage 생성\n",
    "new_message = AIMessage(\n",
    "    content=existing_message.content,\n",
    "    tool_calls=[tool_call],\n",
    "    # 중요! ID는 메시지를 상태에 추가하는 대신 교체하는 방법\n",
    "    id=existing_message.id,\n",
    ")\n",
    "\n",
    "# 수정한 메시지 출력\n",
    "new_message.tool_calls[0][\"args\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13751a91",
   "metadata": {},
   "source": [
    "아래는 업데이트가 되기 전의 메시지입니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "27eb7447",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'name': 'tavily_web_search',\n",
       "  'args': {'query': 'LangGraph 소개 및 자료'},\n",
       "  'id': 'call_xJVDxiPGNJcHmErUMrCEMxdF',\n",
       "  'type': 'tool_call'}]"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 업데이트 전 메시지 확인\n",
    "graph.get_state(to_replay_state.config).values[\"messages\"][-1].tool_calls"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cabf5572",
   "metadata": {},
   "source": [
    "`graph` 에 `update_state` 메서드를 사용하여 상태를 업데이트 합니다.\n",
    "\n",
    "업데이트된 상태를 `updated_state` 에 저장합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "41a6e114",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'configurable': {'thread_id': '6c95af',\n",
       "  'checkpoint_ns': '',\n",
       "  'checkpoint_id': '1ef99eb2-a7bb-6966-8002-a7b0cb3acc65'}}"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 상태 업데이트\n",
    "updated_state = graph.update_state(\n",
    "    to_replay_state.config,\n",
    "    {\"messages\": [new_message]},\n",
    ")\n",
    "updated_state"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9036c5e4",
   "metadata": {},
   "source": [
    "이제 업데이트된 상태를 스트리밍 합니다. 여기서 입력은 `None` 으로 주어 Replay 합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "fe04786b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  tavily_web_search (call_xJVDxiPGNJcHmErUMrCEMxdF)\n",
      " Call ID: call_xJVDxiPGNJcHmErUMrCEMxdF\n",
      "  Args:\n",
      "    query: LangGraph human-in-the-loop workflow site:reddit.com\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: tavily_web_search\n",
      "\n",
      "[\"<document><title>Insights and Learnings from Building a Complex Multi-Agent System</title><url>https://www.reddit.com/r/LangChain/comments/1byz3lr/insights_and_learnings_from_building_a_complex/</url><content>For the chatbot, Chainlit provides everything we need, except background processing. Let's use that. Langchain and LCEL are both flexible and unify the interfaces with the LLMs. We'll need a rather complicated agent workflow, in fact, multiple ones. LangGraph is more flexible than crew.ai or autogen.</content></document>\", \"<document><title>Tool-calling agents: Human approval before tool invocation?</title><url>https://www.reddit.com/r/LangChain/comments/1ci3m0k/toolcalling_agents_human_approval_before_tool/</url><content>Thank you. I've seen both of those examples before - the \\\\\\\"human-in-the-loop\\\\\\\" uses a linear chain (rather than agents). The \\\\\\\"human-as-a-tool\\\\\\\" is when a question can't be answered by the LLM / one of the supplied tools, so it asks a human to supplement with info.</content></document>\", \"<document><title>Flow Engineering with LangChain/LangGraph and CodiumAI - Reddit</title><url>https://www.reddit.com/r/Langchaindev/comments/1dl6pyk/flow_engineering_with_langchainlanggraph_and/</url><content>The talk among Itamar Friedman (CEO of CodiumAI) and Harrison Chase (CEO of LangChain) explores best practices, insights, examples, and hot takes on flow engineering: Flow Engineering with LangChain/LangGraph and CodiumAI. Flow Engineering can be used for many problems involving reasoning, and can outperform naive prompt engineering.</content></document>\"]\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "LangGraph에 대한 유용한 자료를 다음과 같이 추천드립니다:\n",
      "\n",
      "1. **[Insights and Learnings from Building a Complex Multi-Agent System](https://www.reddit.com/r/LangChain/comments/1byz3lr/insights_and_learnings_from_building_a_complex/)** - 이 글에서는 LangGraph의 유연성과 복잡한 에이전트 워크플로우에 대해 논의하고 있습니다. LangGraph가 crew.ai나 autogen보다 더 유연하다는 점을 강조합니다.\n",
      "\n",
      "2. **[Tool-calling agents: Human approval before tool invocation?](https://www.reddit.com/r/LangChain/comments/1ci3m0k/toolcalling_agents_human_approval_before_tool/)** - 이 글에서는 'human-in-the-loop' 접근 방식에 대해 설명하며, LangGraph의 활용 사례를 다룹니다.\n",
      "\n",
      "3. **[Flow Engineering with LangChain/LangGraph and CodiumAI](https://www.reddit.com/r/Langchaindev/comments/1dl6pyk/flow_engineering_with_langchainlanggraph_and/)** - Itamar Friedman과 Harrison Chase가 Flow Engineering의 모범 사례와 통찰을 공유하는 내용으로, LangGraph와 CodiumAI의 결합을 다룹니다. \n",
      "\n",
      "이 자료들은 LangGraph의 기능과 활용에 대한 깊은 이해를 도와줄 것입니다.\n"
     ]
    }
   ],
   "source": [
    "# config 에는 updated_state 를 전달합니다. 이는 임의로 갱신한 상태를 전달하는 것입니다.\n",
    "for event in graph.stream(None, updated_state, stream_mode=\"values\"):\n",
    "    # 메시지가 이벤트에 포함된 경우\n",
    "    if \"messages\" in event:\n",
    "        # 마지막 메시지 출력\n",
    "        event[\"messages\"][-1].pretty_print()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c8dedf06",
   "metadata": {},
   "source": [
    "최종 결과를 출력합니다.\n",
    "\n",
    "이때 사용하는 `config` 는 최종 상태를 가져오는 것이 아니라, 최종 상태를 가져오기 위한 초기 `config` 입니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "24d3e865",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "LangGraph 에 대해서 배워보고 싶습니다. 유용한 자료를 추천해 주세요!\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  tavily_web_search (call_xJVDxiPGNJcHmErUMrCEMxdF)\n",
      " Call ID: call_xJVDxiPGNJcHmErUMrCEMxdF\n",
      "  Args:\n",
      "    query: LangGraph human-in-the-loop workflow site:reddit.com\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "Name: tavily_web_search\n",
      "\n",
      "[\"<document><title>Insights and Learnings from Building a Complex Multi-Agent System</title><url>https://www.reddit.com/r/LangChain/comments/1byz3lr/insights_and_learnings_from_building_a_complex/</url><content>For the chatbot, Chainlit provides everything we need, except background processing. Let's use that. Langchain and LCEL are both flexible and unify the interfaces with the LLMs. We'll need a rather complicated agent workflow, in fact, multiple ones. LangGraph is more flexible than crew.ai or autogen.</content></document>\", \"<document><title>Tool-calling agents: Human approval before tool invocation?</title><url>https://www.reddit.com/r/LangChain/comments/1ci3m0k/toolcalling_agents_human_approval_before_tool/</url><content>Thank you. I've seen both of those examples before - the \\\\\\\"human-in-the-loop\\\\\\\" uses a linear chain (rather than agents). The \\\\\\\"human-as-a-tool\\\\\\\" is when a question can't be answered by the LLM / one of the supplied tools, so it asks a human to supplement with info.</content></document>\", \"<document><title>Flow Engineering with LangChain/LangGraph and CodiumAI - Reddit</title><url>https://www.reddit.com/r/Langchaindev/comments/1dl6pyk/flow_engineering_with_langchainlanggraph_and/</url><content>The talk among Itamar Friedman (CEO of CodiumAI) and Harrison Chase (CEO of LangChain) explores best practices, insights, examples, and hot takes on flow engineering: Flow Engineering with LangChain/LangGraph and CodiumAI. Flow Engineering can be used for many problems involving reasoning, and can outperform naive prompt engineering.</content></document>\"]\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "LangGraph에 대한 유용한 자료를 다음과 같이 추천드립니다:\n",
      "\n",
      "1. **[Insights and Learnings from Building a Complex Multi-Agent System](https://www.reddit.com/r/LangChain/comments/1byz3lr/insights_and_learnings_from_building_a_complex/)** - 이 글에서는 LangGraph의 유연성과 복잡한 에이전트 워크플로우에 대해 논의하고 있습니다. LangGraph가 crew.ai나 autogen보다 더 유연하다는 점을 강조합니다.\n",
      "\n",
      "2. **[Tool-calling agents: Human approval before tool invocation?](https://www.reddit.com/r/LangChain/comments/1ci3m0k/toolcalling_agents_human_approval_before_tool/)** - 이 글에서는 'human-in-the-loop' 접근 방식에 대해 설명하며, LangGraph의 활용 사례를 다룹니다.\n",
      "\n",
      "3. **[Flow Engineering with LangChain/LangGraph and CodiumAI](https://www.reddit.com/r/Langchaindev/comments/1dl6pyk/flow_engineering_with_langchainlanggraph_and/)** - Itamar Friedman과 Harrison Chase가 Flow Engineering의 모범 사례와 통찰을 공유하는 내용으로, LangGraph와 CodiumAI의 결합을 다룹니다. \n",
      "\n",
      "이 자료들은 LangGraph의 기능과 활용에 대한 깊은 이해를 도와줄 것입니다.\n"
     ]
    }
   ],
   "source": [
    "# 최종 결과 출력\n",
    "for msg in graph.get_state(config).values[\"messages\"]:\n",
    "    msg.pretty_print()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-kr-lwwSZlnu-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
